<?php


namespace EchoPHP\Http\Response;

use EchoPHP\Http\Response;

class JsonResponse extends Response {

    protected $data;
    protected $json;
    protected $encodingOptions = 0;
    /**
     * jsonp回调名称
     * @var string
     */
    protected $callback;

    /**
     * json相应构造类
     * @param null $data
     * @param int $status
     * @param array $headers
     * @param bool $json
     * @param int $options
     * @throws \Exception
     */
    public function __construct($data = null, int $status = 200, array $headers = [], bool $json = false, int $options = 0) {
        parent::__construct('', $status, $headers);
        $this->encodingOptions = $options;

        if (is_null($data)) {
            $data = new \ArrayObject();
        }
        $json ? $this->setJson($data) : $this->setData($data);
    }

    /**
     * 创建响应类
     * @param null $data
     * @param int $status
     * @param array $headers
     * @return JsonResponse|Response
     * @throws \Exception
     */
    public static function create($data = null, int $status = 200, array $headers = []) {
        return new static($data, $status, $headers);
    }

    /**
     * 根据json字符串创建响应类
     * @param null $data
     * @param int $status
     * @param array $headers
     * @return JsonResponse
     * @throws \Exception
     */
    public static function createFromJson($data = null, int $status = 200, array $headers = []) {
        return new static($data, $status, $headers, true);
    }

    /**
     * 设置json编码参数
     * @param int $options
     */
    public function setEncodingOptions(int $options) {
        $this->encodingOptions = $options;
    }

    /**
     * 获取当前的json编码参数
     * @return int
     */
    public function getEncodingOptions() {
        return $this->encodingOptions;
    }

    /**
     * 设置响应内容为json字符串
     * @param $json
     * @return $this
     */
    public function setJson($json) {
        $this->data = $json;
        return $this->update();
    }

    /**
     * 将响应内容转换为json字符串并设置到响应中
     * @param array $data
     * @return $this
     * @throws \Exception
     */
    public function setData($data = []) {
        try {
            $data = json_encode($data, $this->encodingOptions);
        } catch (\Exception $e) {
            if ('Exception' === \get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) {
                throw $e->getPrevious() ?: $e;
            }
            throw $e;
        }
        if (JSON_ERROR_NONE !== json_last_error()) {
            throw new \InvalidArgumentException(json_last_error_msg());
        }

        return $this->setJson($data);
    }

    /**
     * 设置jsonp回调名称
     * @param null $callback
     * @return JsonResponse
     */
    public function setCallback($callback = null) {
        if (null !== $callback) {
            // partially taken from http://www.geekality.net/2011/08/03/valid-javascript-identifier/
            // partially taken from https://github.com/willdurand/JsonpCallbackValidator
            //      JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details.
            //      (c) William Durand <william.durand1@gmail.com>
            $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u';
            $reserved = [
                'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while',
                'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export',
                'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false',
            ];
            $parts = explode('.', $callback);
            foreach ($parts as $part) {
                if (!preg_match($pattern, $part) || \in_array($part, $reserved, true)) {
                    throw new \InvalidArgumentException('The callback name is not valid.');
                }
            }
        }

        $this->callback = $callback;

        return $this->update();
    }

    /**
     * 更新响应头信息
     * @return JsonResponse
     */
    protected function update() {
        if (null !== $this->callback) {
            // Not using application/javascript for compatibility reasons with older browsers.
            $this->headers->set('Content-Type', 'text/javascript');

            return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data));
        }
        if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) {
            $this->headers->set('Content-Type', 'application/json');
        }

        return $this->setContent($this->data);
    }
}